﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
using System.IO;
using Goldlight.Mvvm.Base.Infrastructure;
using Goldlight.Mvvm.Interfaces;
using Microsoft.Win32.SafeHandles;

namespace Goldlight.Mvvm.Services
{
    class KTM : IKTM
    {
         /// <summary>
        /// constructor for <see cref="KTMUtility"/>.
        /// </summary>
        KTM() { }

        #region File utilities
        /// <summary>
        /// Creates or opens a file or I/O device. The most commonly used I/O devices 
        /// are as follows: file, file stream, directory, physical disk, 
        /// volume, console buffer, tape drive, communications resource, 
        /// mailslot, and pipe. The function returns a handle that can be used to access the 
        /// file or device for various types of I/O depending on the file or device and the 
        /// flags and attributes specified.
        /// </summary>
        /// <param name="fileName">The name of the file or device to be created or opened.</param>
        /// <param name="fileAccess">The requested access to the file or device, which can be summarized as read, 
        /// write, both or neither (zero).</param>
        /// <param name="fileShare">The requested sharing mode of the file or device, which can be read, 
        /// write, both, delete, all of these, or none (refer to the following table). Access requests 
        /// to attributes or extended attributes are not affected by flag.</param>
        /// <param name="fileMode">An action to take on a file or device that exists or does not exist. </param>
        /// <returns>If the function succeeds, the return value is an open handle to the specified file, 
        /// device, named pipe, or mail slot.</returns>
        public SafeFileHandle TxCreateFile(
            string fileName,
            FileAccess fileAccess,
            FileShare fileShare,
            FileMode fileMode)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                throw new ArgumentException("fileName");
            }

            IntPtr transaction = GetTransactionHandle();
            if (transaction != IntPtr.Zero)
            {
                NativeMethods.FileAccess access = TranslateFileAccess(fileAccess);
                NativeMethods.FileShare share = TranslateFileShare(fileShare);
                return NativeMethods.CreateFileTransacted(
                    fileName, 
                    access, 
                    share, 
                    IntPtr.Zero,
                    TranslateFileMode(fileMode), 
                    0, 
                    IntPtr.Zero, 
                    transaction, 
                    IntPtr.Zero, 
                    IntPtr.Zero);
            }
            return NativeMethods.CreateFile(
                fileName,
                TranslateFileAccess(fileAccess),
                TranslateFileShare(fileShare),
                IntPtr.Zero,
                TranslateFileMode(fileMode),
                0,
                IntPtr.Zero);
        }

        /// <summary>
        /// Copies an existing file to a new file as a transacted operation (if applicable), 
        /// notifying the application of its progress through a callback function.
        /// </summary>
        /// <param name="existingFileName">The name of an existing file. </param>
        /// <param name="newFileName">The name of the new file. </param>
        /// <param name="callback">The <see cref="CopyProgressCallback"/> called each time
        /// a portion of the file has been copied.</param>
        public bool TxCopy(FileInfo existingFileName, string newFileName, NativeMethods.CopyProgressCallback callback)
        {
            return TxCopy(existingFileName, newFileName, callback, NativeMethods.CopyFileFlags.COPY_FILE_ALLOW_DECRYPTED_DESTINATION | NativeMethods.CopyFileFlags.COPY_FILE_FAIL_IF_EXISTS | NativeMethods.CopyFileFlags.COPY_FILE_OPEN_SOURCE_FOR_WRITE | NativeMethods.CopyFileFlags.COPY_FILE_RESTARTABLE);
        }

        /// <summary>
        /// Copies an existing file to a new file as a transacted operation (if applicable), 
        /// notifying the application of its progress through a callback function.
        /// </summary>
        /// <param name="existingFileName">The name of an existing file. </param>
        /// <param name="newFileName">The name of the new file. </param>
        /// <param name="callback">The <see cref="CopyProgressCallback"/> called each time
        /// a portion of the file has been copied.</param>
        /// <param name="flags"></param>
        /// <returns>True if the copy was successful, false otherwise.</returns>
        public bool TxCopy(FileInfo existingFileName, 
            string newFileName, 
            NativeMethods.CopyProgressCallback callback, 
            NativeMethods.CopyFileFlags flags)
        {
            IntPtr transaction = GetTransactionHandle();
            int cancel = 0;
            if (transaction != IntPtr.Zero)
            {
                return NativeMethods.CopyFileTransacted(
                    existingFileName.FullName,
                    newFileName,
                    callback,
                    IntPtr.Zero,
                    ref cancel,
                    flags,
                    transaction);
            }
            return NativeMethods.CopyFile(
                existingFileName.FullName, 
                newFileName, 
                callback,
                IntPtr.Zero,
                ref cancel,
                flags);
        }

        /// <summary>
        /// Moves an existing file to a new file as a transacted operation (if applicable), 
        /// notifying the application of its progress through a callback function.
        /// </summary>
        /// <param name="existingFileName">The name of an existing file. </param>
        /// <param name="newFileName">The name of the new file. </param>
        /// <param name="callback">The <see cref="CopyProgressCallback"/> called each time
        /// a portion of the file has been copied.</param>
        public bool TxMove(FileInfo existingFileName, string newFileName, NativeMethods.CopyProgressCallback callback)
        {
            NativeMethods.MoveFileFlags flags = NativeMethods.MoveFileFlags.MOVEFILE_COPY_ALLOWED |
                NativeMethods.MoveFileFlags.MOVEFILE_REPLACE_EXISTING | 
                NativeMethods.MoveFileFlags.MOVEFILE_WRITE_THROUGH;
            return TxMove(existingFileName, newFileName, callback, flags);
        }

        /// <summary>
        /// Moves an existing file to a new file as a transacted operation (if applicable), 
        /// notifying the application of its progress through a callback function.
        /// </summary>
        /// <param name="existingFileName">The name of an existing file. </param>
        /// <param name="newFileName">The name of the new file. </param>
        /// <param name="callback">The <see cref="CopyProgressCallback"/> called each time
        /// a portion of the file has been copied.</param>
        public bool TxMove(FileInfo existingFileName, string newFileName, NativeMethods.CopyProgressCallback callback, NativeMethods.MoveFileFlags flags)
        {
            IntPtr transaction = GetTransactionHandle();
            if (transaction != IntPtr.Zero)
            {
                return NativeMethods.MoveFileTransacted(
                    existingFileName.FullName,
                    newFileName,
                    callback,
                    IntPtr.Zero,
                    flags,
                    transaction);
            }
            return NativeMethods.MoveFile(
                existingFileName.FullName,
                newFileName,
                callback,
                IntPtr.Zero,
                flags);
        }

        /// <summary>
        /// Permanently delete a file.
        /// </summary>
        /// <param name="value">The file to delete.</param>
        /// <remarks>
        /// If delete is not performed inside a transaction, or if the application
        /// is running on a lower OS than Vista, the file will be deleted none-transactionally; in
        /// other cases, the file will be deleted inside a transaction.
        /// </remarks>
        public bool TxDelete(FileInfo value)
        {
            IntPtr transaction = GetTransactionHandle();
            if (transaction != IntPtr.Zero)
            {
                return NativeMethods.DeleteFileTransacted(value.FullName, transaction);
            }
            else
            {
                value.Delete();
            }
            return true;
        }
        #endregion

        #region Directory utilities
        /// <summary>
        /// Deletes <see cref="System.IO.DirectoryInfo"/> if it is empty.
        /// </summary>
        /// <param name="value">The directory to delete.</param>
        /// <remarks>
        /// If delete is not performed inside a transaction, or if the application
        /// is running on a lower OS than Vista, the directory will be deleted none-transactionally; in
        /// other cases, the directory will be deleted inside a transaction.
        /// </remarks>
        public bool TxDelete(DirectoryInfo value)
        {
            return TxDelete(value, false);
        }

        /// <summary>
        /// Deletes instance of a <see cref="System.IO.DirectoryInfo"/>,
        /// specifying whether to delete a subdirectory and files.
        /// </summary>
        /// <param name="value">The directory to delete.</param>
        /// <param name="recursive">If true, delete subdirectories and files.</param>
        /// <remarks>
        /// If delete is not performed inside a transaction, or if the application
        /// is running on a lower OS than Vista, the directory will be deleted none-transactionally; in
        /// other cases, the directory will be deleted inside a transaction.
        /// </remarks>
        public bool TxDelete(DirectoryInfo value, bool recursive)
        {
            IntPtr transaction = GetTransactionHandle();
            if (transaction != IntPtr.Zero)
            {
                return NativeMethods.RemoveDirectoryTransacted(value.FullName, transaction);
            }
            else
            {
                value.Delete(recursive);
            }
            return true;
        }

        /// <summary>
        /// Create all directories and subdirectories as specified by the path.
        /// </summary>
        /// <param name="path">The directory to create.</param>
        public bool TxCreateDirectory(string path)
        {
            IntPtr transaction = GetTransactionHandle();
            if (transaction != IntPtr.Zero)
            {
                return NativeMethods.CreateDirectoryTransacted(null, path, IntPtr.Zero, transaction);
            }
            else
            {
                Directory.CreateDirectory(path);
            }
            return true;
        }
        #endregion

        #region Private methods
        private NativeMethods.FileMode TranslateFileMode(FileMode mode)
        {
            if (mode != FileMode.Append)
            {
                return (NativeMethods.FileMode)(int)mode;
            }
            else
            {
                return (NativeMethods.FileMode)(int)FileMode.OpenOrCreate;
            }
        }

        private NativeMethods.FileAccess TranslateFileAccess(FileAccess access)
        {
            return access == FileAccess.Read ? NativeMethods.FileAccess.GENERIC_READ : NativeMethods.FileAccess.GENERIC_WRITE;
        }

        private NativeMethods.FileShare TranslateFileShare(FileShare share)
        {
            return (NativeMethods.FileShare)(int)share;
        }

        /// <summary>
        /// Is the application currently inside a Transaction and 
        /// is application running on Vista or higher?
        /// </summary>
        private bool InsideTransaction
        {
            get
            {
                return Environment.OSVersion.Version.Major > 5 && Transaction.Current != null;
            }
        }

        /// <summary>
        /// Get a handle to the current DTC transaction.
        /// </summary>
        /// <returns>The handle if we are currently inside a transaction, IntPtr.Zero if not.</returns>
        private IntPtr GetTransactionHandle()
        {
            IntPtr transactionHandle = IntPtr.Zero;
            if (InsideTransaction)
            {
                IKernelTx tx = (IKernelTx)TransactionInterop.GetDtcTransaction(Transaction.Current);
                tx.GetHandle(out transactionHandle);
            }
            return transactionHandle;
        }
        #endregion

   }
}